package nbcp.base.model


import nbcp.base.comm.config
import nbcp.base.db.KeyValueString
import nbcp.base.event.HttpRequestEvent
import nbcp.base.extend.*
import nbcp.base.utils.CodeUtil
import nbcp.base.utils.FileUtil.resolvePath
import nbcp.base.utils.SpringUtil
import nbcp.base.utils.StringUtil
import nbcp.base.utils.UrlUtil
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.apache.commons.collections4.map.CaseInsensitiveMap
import org.slf4j.LoggerFactory
import java.io.File
import java.util.concurrent.TimeUnit
import java.util.function.Function
import java.util.stream.Collectors


class HttpRequestModel {
    var name: String = ""
    val requestHeader: CaseInsensitiveMap<String, String> = CaseInsensitiveMap()

    var result: HttpResponseModel? = null;


    /**
     * 表示查询参数, 查询参数可以重复,所以不能用 Map
     */
    val queryParameters: ArrayList<KeyValueString> = ArrayList<KeyValueString>()
    var postObject: Any? = null
    var postString: String? = null

    var url: String = ""
    var requestMethod: String = ""

    /**
     * 定义路径，不是执行路径！
     */
    var defPath: String = ""


    val withQueryUrl: String
        /**
         * 获取带查询参数的url
         *
         * @return
         */
        get() {
            var url = this.url
            if (queryParameters.size > 0) {
                if (!url.contains("?")) {
                    url += "?"
                }


                url += queryParameters
                    .map {
                        UrlUtil.encodeURIComponent(it.key) + "=" + UrlUtil.encodeURIComponent(
                            it.value
                        )
                    }
                    .joinToString("&")
            }

            return url
        }


    /**
     * 添加请求头
     *
     * @param key
     * @param value
     */
    fun addHeader(key: String, value: String) {
        requestHeader[key] = value
    }

    /**
     * 添加查询参数
     *
     * @param key
     * @param value
     */
    fun addQuery(key: String?, value: String?) {
        if (key.isNullOrEmpty()) {
            return
        }

        if (value == null) {
            return
        }

        queryParameters.add(KeyValueString(key, value))
    }


    /**
     * 添加 数组类型 的参数值
     *
     * @param key
     * @param values
     */
    fun addQueryArrayParameter(key: String, values: Array<String>) {
        addQueryListParameter(key, values.toList())
    }


    /**
     * 添加 List 类型的参数值
     *
     * @param key
     * @param values
     */
    fun addQueryListParameter(key: String, values: List<String>) {
        if (key.isNullOrEmpty()) {
            return
        }

        if (values.isNullOrEmpty()) {
            return
        }


        /*
- 1. 服务器端定义  Integer[]  , 前端用 key=1,2,3
- 2. 服务器端定义  @RequestParam("key"] Integer[]  , 前端用 key=1,2,3
- 3. 服务器端定义  @RequestParam("key")  List<Integer>, 前端用 key=1,2,3
- 4. 服务器端定义  @RequestParam("key[]")  Integer[], 前端用 key[]=1&key[]=2&key[]=3
- 5. 服务器端定义  @RequestParam("key[]")  List<Integer>, 前端用 key[]=1&key[]=2&key[]=3
         */
        if (key.endsWith("[]")) {
            for (v in values) {
                queryParameters.add(
                    KeyValueString(key, v)
                )
            }

            return
        }


        queryParameters.add(
            KeyValueString(key, java.lang.String.join(",", values))
        )
    }


    /**
     * 添加对象类型参数值
     *
     * @param key
     * @param parameterValue
     */
    fun addQueryObject(key: String, parameterValue: Any?) {
        if (parameterValue == null) {
            return
        }

        val v_type: Class<*> = parameterValue.javaClass
        if (v_type.IsSimpleType()) {
            this.addQuery(key, parameterValue.toString())
            return
        } else if (v_type.isArray) {
            val list = (parameterValue as Array<Any?>)
                .map { it.toString() }


            this.addQueryListParameter(key, list)
            return
        } else if (v_type.IsCollectionType) {
            val list = (parameterValue as Collection<Any?>)
                .map { it.toString() }


            this.addQueryListParameter(key, list)
            return
        }

        throw RuntimeException(
            "HttpClient 传递参数时,发现不识别的类型: " + v_type.name + ", 值: " + parameterValue.ToJson()
        )
    }


    var postBytes: ByteArray? = null
    var uploadFileName: String? = null

    /**
     * 设置上传内容
     *
     * @param fileName  文件名
     * @param postBytes 内容
     */
    fun setPostFileBytes(fileName: String, postBytes: ByteArray?) {
        this.uploadFileName = if (fileName.isNullOrEmpty()) "file" else fileName
            .replace("[\\\\\\/\\?\\s\\:\\|\\>\\<\\*]".toRegex(), "")
        this.postBytes = postBytes
    }


    fun doInvoke(): HttpResponseModel {
        var requestModel = this;
        if (requestModel.name.HasValue) {
            this.name = requestModel.name
        }
        val headers = Headers.Builder()

        for ((key, value) in requestModel.requestHeader) {
            headers.add(key, value)
        }


        val url = requestModel.withQueryUrl


        val request = Request.Builder()

        try {
            request.url(url)
        } catch (e: Exception) {
            throw RuntimeException("url 设置异常,url: " + url + " " + e.message)
        }

        request.headers(headers.build())


        if ("GET".equals(requestModel.requestMethod, ignoreCase = true)) {
            request.get()
        } else {
            var postContent = requestModel.postString
            val postObject = requestModel.postObject
            if (postContent == null && postObject != null) {
                postContent = postObject.ToJson()
            }

            var requestBody: RequestBody? = null
            if (postContent != null) {
                requestBody = postContent!!.toRequestBody(
                    "application/json".toMediaType()
                )
            } else {
                val postBytes = requestModel.postBytes
                if (postBytes != null) {
                    val bodyBuilder = MultipartBody.Builder()
                    bodyBuilder.setType(MultipartBody.FORM)

                    val uploadFileName = requestModel.uploadFileName
                    var file: File? = null

                    file = if (uploadFileName.HasValue) {
                        File(
                            resolvePath(
                                System.getProperty("java.io.tmpdir"),
                                "http-util-" + CodeUtil.getCode(),
                                uploadFileName!!
                            )
                        )
                    } else {
                        File(
                            resolvePath(
                                System.getProperty("java.io.tmpdir"),
                                "http-util-" + CodeUtil.getCode()
                            )
                        )
                    }

                    file.writeBytes(postBytes)

                    bodyBuilder.addFormDataPart(
                        "file",
                        uploadFileName.AsString("file"),
                        file!!.asRequestBody("application/octet-stream".toMediaType())
                    )

                    requestBody = bodyBuilder.build()
                }
            }


            if (requestBody == null) {
                requestBody = FormBody.Builder().build()
            }

            val method = requestModel.requestMethod
            if ("POST".equals(method, ignoreCase = true)) {
                request.post(requestBody)
            } else if ("PUT".equals(method, ignoreCase = true)) {
                request.put(requestBody)
            } else if ("DELETE".equals(method, ignoreCase = true)) {
                request.delete(requestBody)
            }
        }


        //https://www.cnblogs.com/greyzeng/p/14148675.html
        val client = OkHttpClient.Builder()
            .connectionPool(CONNECTION_POOL)
            .connectTimeout(
                config.getConfig("app.http-client.pool.connectTimeoutSeconds", 3L),
                TimeUnit.SECONDS
            )
            .readTimeout(
                config.getConfig("app.http-client.pool.readTimeoutSeconds", 15L),
                TimeUnit.SECONDS
            )
            .build()

        var ev = HttpRequestEvent(this);
        if( SpringUtil.isInited) {
            SpringUtil.context.publishEvent(ev);
        }

        val result = client.newCall(request.build()).execute()

        val responseModel = HttpResponseModel()
        this.result = responseModel;


        val resHeaders = result.headers
        for (item in resHeaders) {
            responseModel.addHeader(item.component1(), item.component2())
        }

        if (contentTypeIsTextType(result.header("content-type"))) {
            responseModel.responseBody = result.body!!.string()
        }
        responseModel.status = result.code

        logInfo(requestModel, responseModel)

        return responseModel
    }


    fun getLogMessage(): String {
        var request = this;
        var url = request.withQueryUrl;


        return "${request.requestMethod} ${url} ◈${
            request.requestHeader.keys
                .stream()
                .map { it: String -> it + ": " + request.requestHeader[it] }
                .collect(Collectors.joining(" , "))
        } ◈${request.postString.AsString { request.postObject.ToJson() }}"
    }


    private fun logInfo(requestModel: HttpRequestModel, responseModel: HttpResponseModel) {
        val responseHeader = responseModel.responseHeader
        val responseBody = responseModel.responseBody
        val method = requestModel.requestMethod
        val url = requestModel.withQueryUrl
        val status = responseModel.status

        if (responseBody == null) {
            logErrorWithResponse(requestModel, responseModel)
            return
        }

        if (status >= 400) {
            logErrorWithResponse(requestModel, responseModel)
            return
        }


        var logBody = responseBody
        if (logBody.length > 1024) {
            logBody = logBody.substring(0, 1024) + " ..."
        }


        log.info("[{}] {} {} ◈{} ◈{} ◈{}",
            status,
            method,
            url,
            requestModel.requestHeader.keys
                .stream()
                .map<String> { it: String -> it + ": " + requestModel.requestHeader[it] }
                .collect(Collectors.joining(" , ")),
            logBody,
            responseHeader.keys
                .map { it.toString() + ": " + responseHeader.get(it) }
                .joinToString(" , ")
        )
    }

    private fun logErrorWithResponse(requestModel: HttpRequestModel, responseModel: HttpResponseModel) {
        val responseHeader = responseModel.responseHeader
        val responseBody = responseModel.responseBody
        val method = requestModel.requestMethod
        val url = requestModel.withQueryUrl
        val status = responseModel.status

        var logBody = responseBody.AsString()
        if (logBody.length > 1024) {
            logBody = logBody.substring(0, 1024) + " ..."
        }


        log.error("[{}] {} {} ◈{} ◈{} ◈{}",
            status,
            method,
            url,
            requestModel.requestHeader.keys
                .stream()
                .map { it: String -> it + ": " + requestModel.requestHeader[it] }
                .collect(Collectors.joining(" , ")),
            logBody,
            responseHeader.keys
                .map { it.toString() + ": " + responseHeader.get(it) }
                .joinToString(" , ")
        )
    }


    companion object {
        val CONNECTION_POOL: ConnectionPool = ConnectionPool(256, 180L, TimeUnit.SECONDS)


        /**
         * 判断是否是文本类型
         *
         * @param contentType
         * @return
         */
        fun contentTypeIsTextType(contentType: String?): Boolean {
            if (contentType == null) {
                return false
            }
            if (contentType.contains("json")) {
                return true
            } else if (contentType.contains("text") || contentType.contains("plain")) {
                return true
            } else if (contentType.contains("html") || contentType.contains("xml")) {
                return true
            }
            return false
        }

        private val log = LoggerFactory.getLogger(this::class.java.declaringClass)
    }
}
